using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;

namespace GwLib.JqGridMvc
{
    public class ReflectionUtility
    {
        private static string eap;


        public static string EntryAssemblyPath
        {
            get
            {
                if (eap == null)
                {
                    eap = Assembly.GetEntryAssembly().Location;
                    eap = eap.Substring(0, eap.LastIndexOf(@"\"));
                }
                return eap;
            }
        }

        public static DirectoryInfo EntryAssemblyDirectoryInfo
        {
            get
            {
                return new DirectoryInfo(EntryAssemblyPath);
            }
        }

        public static string ExecutingAssemblyPath
        {
            get
            {
                if (eap == null)
                {
                    eap = Assembly.GetExecutingAssembly().Location;
                    eap = eap.Substring(0, eap.LastIndexOf(@"\"));
                }
                return eap;
            }
        }

        public static DirectoryInfo ExecutingAssemblyDirectoryInfo
        {
            get
            {
                return new DirectoryInfo(ExecutingAssemblyPath);
            }
        }

        public static PropertyInfo GetPropertyFromExpression(Expression expression)
        {
            PropertyInfo prop = null;
            if (expression is MemberExpression)
            {
                prop = (expression as MemberExpression).Member as PropertyInfo;
            }
            else if (expression is UnaryExpression)
            {
                var unaryExp = (expression as UnaryExpression);
                prop = (unaryExp.Operand as MemberExpression).Member as PropertyInfo;
            }
            return prop;
        }

        public static object[] InstantiateClassesByType(Assembly assembly, System.Type matchingType)
        {
            if (matchingType.IsInterface)
            {
                return InstantiateClassesByInterfaceType(assembly, matchingType);
            }
            else
            {
                return InstantiateClassesByClassType(assembly, matchingType);
            }
        }

        public static List<T> InstantiateClasses<T>(Assembly assembly)
        {
            Type t = typeof(T);
            if (t.IsInterface)
            {
                return InstantiateClassesByInterface<T>(assembly);
            }
            else
            {
                return InstantiateClassesByClass<T>(assembly);
            }
        }

        private static List<T> InstantiateClassesByInterface<T>(Assembly assembly)
        {
            Type t = typeof(T);
            List<T> list = new List<T>();

            object[] objs = InstantiateClassesByInterfaceType(assembly, t);

            foreach (T item in objs)
            {
                list.Add(item);
            }

            return list;
        }

        private static List<T> InstantiateClassesByClass<T>(Assembly assembly)
        {
            Type t = typeof(T);
            List<T> list = new List<T>();

            object[] objs = InstantiateClassesByClassType(assembly, t);
            
            foreach (T item in objs) 
            {
                list.Add(item);
            }

            return list;
        }

        private static object[] InstantiateClassesByClassType(Assembly assembly, System.Type matchingType)
        {
            List<Object> objects = new List<Object>();

            //cant call the entry method if the assembly is null
            if (assembly != null)
            {
                bool classFound = false;

                //Use reflection to call the static Run function
                Module[] mods = assembly.GetModules(false);
                Type[] types = mods[0].GetTypes();

                //loop through each class that was defined and look for the first occurrance of the entry point method
                foreach (Type type in types)
                {
                    if (((type == matchingType) || (type.IsSubclassOf(matchingType))) &&
                        !type.IsAbstract)
                    {
                        ConstructorInfo[] constructors = type.GetConstructors();
                        ConstructorInfo constructor = constructors[0];

                        objects.Add(constructor.Invoke(BindingFlags.CreateInstance, null, new object[] { }, null));
                        classFound = true;
                    }
                }

                //if it got here, then there was no entry point method defined.  Tell user about it
                if (!classFound)
                {
                    //TODO: handle error with correct Exception Type or just log the error
                }
            }

            return objects.ToArray();
        }

        private static object[] InstantiateClassesByInterfaceType(Assembly assembly, System.Type matchingType)
        {
            List<Object> objects = new List<Object>();

            //cant call the entry method if the assembly is null
            if (assembly != null)
            {
                bool classFound = false;

                //Use reflection to call the static Run function
                Module[] mods = assembly.GetModules(false);
                Type[] types = mods[0].GetTypes();

                //loop through each class that was defined and look for the first occurrance of the entry point method
                foreach (Type type in types)
                {
                    if (!type.IsAbstract)
                    {
                        Type[] interfaces = type.GetInterfaces();
                        foreach (Type iface in interfaces)
                        {
                            if (iface == matchingType)
                            {
                                ConstructorInfo[] constructors = type.GetConstructors();
                                ConstructorInfo constructor = constructors[0];

                                objects.Add(constructor.Invoke(BindingFlags.CreateInstance, null, new object[] { }, null));
                                classFound = true;
                                break;
                            }
                        }
                    }
                }

                //if it got here, then there was no entry point method defined.  Tell user about it
                if (!classFound)
                {
                    //TODO: handle error with correct Exception Type or just log the error
                }
            }

            return objects.ToArray();
        }

        public static Assembly LoadDynamicAssembly(string assemblyName)
        {
            string savedDirectory = System.Environment.CurrentDirectory;
            System.Environment.CurrentDirectory = eap;

            string tmpName = assemblyName;
            if (tmpName.ToLower().EndsWith(".dll"))
            {
                tmpName = tmpName.Substring(0, tmpName.Length - 4);
            }
            AssemblyName name = new AssemblyName(tmpName);
            Assembly assembly = Assembly.Load(name);

            System.Environment.CurrentDirectory = savedDirectory;

            return assembly;
        }

        public static Assembly CompileCSharpAssembly(string[] sourceFiles, string assemblyName, List<string> references, out List<CompilerError> errors)
        {
            string savedDirectory = System.Environment.CurrentDirectory;
            System.Environment.CurrentDirectory = eap;

            Assembly generatedAssembly = null;
            errors = null;

            //Create an instance whichever code provider that is needed
            CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("C#");

            //add compiler parameters
            CompilerParameters compilerParams = new CompilerParameters();
            compilerParams.CompilerOptions = "/target:library /optimize /platform:x86";

            // Add References
            foreach (string reference in references)
            {
                compilerParams.ReferencedAssemblies.Add(reference);
            }

            compilerParams.GenerateExecutable = false;
            compilerParams.IncludeDebugInformation = false;
            compilerParams.GenerateInMemory = false;
            compilerParams.OutputAssembly = assemblyName + ".dll";

            CompilerResults results = codeProvider.CompileAssemblyFromFile(compilerParams, sourceFiles);

            //Do we have any compiler errors
            if (results.Errors.HasErrors)
            {
                errors = new List<CompilerError>();
                foreach (CompilerError compileError in results.Errors)
                {
                    errors.Add(compileError);
                }
            }
            else
            {
                //get a hold of the actual assembly that was generated
                generatedAssembly = results.CompiledAssembly;
            }

            System.Environment.CurrentDirectory = savedDirectory;

            //return the assembly
            return generatedAssembly;
        }
    }
}
